
#include "../iface/debug.h"
#include <apr-1/apr_network_io.h>
#include "../iface/APRInterface.h"
#include "TCPClient.h"

// TCPClient using blocking sockets so that the while (1) loops don't spin!

/** Constructor for the basic TCP client class. This connects immediately.
 * 
 * @param ihostname
 * Remote host name to connect to.
 * 
 * @param iportno
 * Remote TCP port number to connect to.
 * 
 * @param imp
 * Memory pool from which to derived the memory pool for this class. */
TCPClient::TCPClient(const char *ihostname, int iportno, apr_pool_t *imp)
{
   apr_status_t status;
   status = apr_pool_create(&mp, imp);
   CheckAPRError(status);

   status = apr_sockaddr_info_get(&addrstruct, ihostname, APR_INET, iportno, 0, mp);
   CheckAPRError(status);
   status = apr_socket_create(&tsocket, addrstruct->family, SOCK_STREAM, APR_PROTO_TCP, mp);
   CheckAPRError(status);
   status = apr_socket_connect(tsocket, addrstruct);
   CheckAPRError(status);
   status = apr_socket_opt_set(tsocket, APR_SO_NONBLOCK, 0);
   CheckAPRError(status);
   status = apr_socket_opt_set(tsocket, APR_TCP_NODELAY, 0);
   CheckAPRError(status);
   status = apr_socket_timeout_set(tsocket, CLIENT_SOCKET_TIMEOUT * 1000);
   CheckAPRError(status);
   clientbb = new APRBucketBrigade(mp);
   clientbb->addSocketBucket(tsocket);
   clientbb->writeEOS();
}

/** Destructor for the basic TCP client class. This disconnects. */
TCPClient::~TCPClient()
{
   if (mp == NULL) return;
   apr_status_t status;
   status = apr_socket_close(tsocket);
   CheckAPRError(status);
   if (clientbb) delete clientbb;
   apr_pool_clear(mp);
   apr_pool_destroy(mp);
   mp = NULL;
}

/** Send a buffer of data over the opened connection.
 * 
 * @param buf
 * Buffer of data.
 * 
 * @param buflen
 * Length of the data in the buffer.
 * 
 * @return
 * Number of bytes sent. */
apr_off_t TCPClient::SendBuffer(const char *buf, apr_off_t buflen)
{
   apr_status_t status;
   apr_size_t sent = 0;
   apr_off_t newlen = (buflen == -1) ? strlen(buf) : buflen;
   while (1)
   {
      apr_size_t len = newlen - sent;
      if (len == 0) break;
      status = apr_socket_send(tsocket, &(buf[sent]), &len);
      CheckAPRError(status);
      if (status != 0) break;
      sent += len;
   }
   return sent;
}

/** Send a pipe of data over the opened connection.
 * 
 * @param aprbb
 * An APR bucket brigade pipe of input data.
 * 
 * @return
 * Number of bytes sent. */
apr_off_t TCPClient::SendBucketBrigade(APRBucketBrigade *aprbb)
{
   apr_status_t status;
   apr_size_t sent = 0;
   char *buf = (char *)malloc(sizeof(char)*MAX_BLOCK_SIZE);
   while (1)
   {
      apr_size_t len = MAX_BLOCK_SIZE;
      apr_size_t newlen = aprbb->readFromStream(buf, len);
      if ((newlen == 0) && (aprbb->isClosed())) break;
      apr_size_t tsent = 0;
      while (1)
      {
         apr_size_t newlen2 = newlen - tsent;
         if (newlen2 == 0) break;
         status = apr_socket_send(tsocket, &(buf[tsent]), &newlen2);
         CheckAPRError(status);
         if (status != 0) break;
         tsent += newlen2;
      }
      if (status != 0) break;
      aprbb->removeFromStream(newlen);
      sent += newlen;
   }
   free(buf);
   return sent;
}

/** Receive some data over the connection
 * 
 * @param len
 * Length of data to receive.
 * 
 * @return
 * A vector buffer of received data. */
std::vector<char> *TCPClient::RecvBuffer(apr_off_t len)
{
   apr_off_t actualreq;
   std::vector<char> *returned = new std::vector<char>();
   char *buf = (char *)malloc(sizeof(char)*MAX_BLOCK_SIZE);
   while (1)
   {
      actualreq = (MAX_BLOCK_SIZE < (len - returned->size())) ? MAX_BLOCK_SIZE : (len - returned->size());
      if (len == -1) actualreq = MAX_BLOCK_SIZE;
      apr_size_t len = clientbb->popFromStream(buf, actualreq);
      if (clientbb->isClosed()) break;
      for (apr_off_t i = 0; i < len; i++)
         returned->push_back(buf[i]);
   }
   free(buf);
   return returned;
}

/** Send and then receive a buffer of data.
 * 
 * @param buf
 * Buffer of data to be sent.
 * 
 * @param sendlen
 * Length of data to send.
 * 
 * @param recvlen
 * Length of data to receive.
 * 
 * @return
 * A vector buffer of receive data. */
std::vector<char> *TCPClient::SendAndRecvBuffer(const char *buf, apr_off_t sendlen, apr_off_t recvlen)
{
   apr_off_t newlen = (sendlen == -1) ? strlen(buf) : sendlen;
   apr_off_t len2 = SendBuffer(buf, newlen);
   if (newlen != len2)
   {
      errprint("Could not complete data sending");
      return NULL;
   }
   std::vector<char> *returned = RecvBuffer(recvlen);
   return returned;
}
